﻿using System.Diagnostics;
using System.Net;
using System.Net.Sockets;

using BodaNetCommunications.Cores;
using BodaNetCommunications.Utilities.Locks;
using BodaNetCommunications.Utilities.Results;

namespace BodaNetCommunications.ModbusComs
{
    public class CommUnitModbusTcp : ICommUnit
    {
        /// <summary>
        /// 交互的混合锁
        /// </summary>
        protected SimpleHybirdLock InteractiveLock;      // 一次正常的交互的互斥锁

        private Socket _socket;//   TCP
                               //   UDP    = new Socket();

        public bool IsConnected
        {
            get
            {
                return _socket != null && !(!_socket.Connected || _socket.Poll(200, SelectMode.SelectRead) && _socket.Available == 0);
            }
        }

        public CommUnitModbusTcp(ConnectionOptions options)
        {
            if (string.IsNullOrEmpty(options.Ip) || false == IPAddress.TryParse(options.Ip, out _))
                throw new Exception($"Ip[{options.Ip}]为空或者格式不正确");

            if (options.Port < 0 || options.Port > 65535)
                throw new Exception($"Port[{options.Port}]不正确");

            InteractiveLock ??= new SimpleHybirdLock();

            // 如果为空，则重新创建
            _socket ??= new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            _socket.ReceiveTimeout = options.ReadTimeout;
            ReadTimeout = options.ReadTimeout;
            IP = options.Ip;
            Port = options.Port;
        }
        private string IP { get; set; }
        private int Port { get; set; }
        private int ReadTimeout { get; set; } = 50;
        public int ConnectTimeout { get; set; }

        public EndPoint? LocalEndPoint { get => _socket?.LocalEndPoint; }

        private ManualResetEvent _timeoutObject = new ManualResetEvent(false);

        public void Close()
        {
            if (_socket != null && _socket.Connected)
            {
                _socket.Shutdown(SocketShutdown.Both);
                _socket.Dispose();
            }
        }

        bool _connectState = false;
        public BodaResult<bool> Open(int timeout)
        {
            BodaResult<bool> result = new BodaResult<bool>()
            {
                IsSuccess = false
            };
            try
            {
                Stopwatch stopwatch = new Stopwatch();
                stopwatch.Start();
                while (stopwatch.ElapsedMilliseconds < timeout)
                {
                    if (_socket != null && !(!_socket.Connected || _socket.Poll(200, SelectMode.SelectRead) && _socket.Available == 0))
                    {
                        result.IsSuccess = true;
                        return result;
                    }
                    try
                    {
                        _connectState = false;
                        _socket?.Close();
                        _socket?.Dispose();
                        _socket = null;

                        _socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                        _socket.ReceiveTimeout = ReadTimeout;
                        _socket.BeginConnect(IP, Port, callback =>
                        {
                            try
                            {
                                var cbSocket = callback.AsyncState as Socket;
                                if (cbSocket != null)
                                {
                                    _connectState = cbSocket.Connected;

                                    if (cbSocket.Connected)
                                        cbSocket.EndConnect(callback);

                                }
                            }
                            catch (Exception ex)
                            {
                                result.IsSuccess = false;
                                result.Message = ex.Message;
                            }
                            _timeoutObject.Set();
                        }, _socket);
                        _timeoutObject.WaitOne(timeout, false);
                        if (_connectState)
                        {
                            result.IsSuccess = true;
                            break;
                        }
                    }
                    catch (SocketException ex)
                    {
                        if (ex.ErrorCode == 10060)
                        {
                            result.IsSuccess = false;
                            result.Message = ex.Message;
                        }
                    }
                    catch (Exception ex)
                    {
                        result.IsSuccess = false;
                        result.Message = ex.Message;
                    }
                }
                if (_socket == null || !_socket.Connected || _socket.Poll(200, SelectMode.SelectRead) && _socket.Available == 0)
                {
                    result.IsSuccess = false;
                    result.Message = "网络连接失败";
                }
            }
            catch (Exception ex)
            {
                result.IsSuccess = false;
                result.Message = ex.Message;
            }
            return result;
        }

        public BodaResult<byte> SendAndReceive(List<byte> req, int receiveLen, int errorLen)
        {
            BodaResult<byte> result = new BodaResult<byte>();

            InteractiveLock.Enter();
            try
            {
                // 同步
                _socket.Send(req.ToArray(), 0, req.Count, SocketFlags.None);

                // 获取指定长度：MBAP  头部
                byte[] headerBytes = new byte[6];
                int count = _socket.Receive(headerBytes, 0, 6, SocketFlags.None);
                if (count == 0)
                    throw new Exception("未接收到响应数据");
                // 数据PDU部分数据
                int pduLen = BitConverter.ToInt16(new byte[] { headerBytes[5], headerBytes[4] });
                byte[] pduBytes = new byte[pduLen];
                count = _socket.Receive(pduBytes, 0, pduLen, SocketFlags.None);
                if (count == 0)
                    throw new Exception("未接收到响应数据");

                result.Datas.AddRange(headerBytes);
                result.Datas.AddRange(pduBytes);
            }
            catch (Exception ex)
            {
                result.IsSuccess = false;
                result.Message = ex.Message;
            }
            finally
            {
                InteractiveLock.Leave();
            }
            return result;
        }

        // 异步：从发送到接收的这个过程
        object lockObj = new object();
        public void SendAsync(List<byte> reqBytes, Action<BodaResult<byte>> callback)
        {
            /// 有三方式可以做为异步解决方案
            /// 1、按同步思路调整为异步，使用Task
            /// 2、使用Socket的BeginReceive方法
            /// 3、发送与接收分离
            _socket.Send(reqBytes.ToArray(), 0, reqBytes.Count, SocketFlags.None);

            Task.Run(() =>
            {
                lock (lockObj)
                {
                    BodaResult<byte> result = new BodaResult<byte>();
                    // 获取指定长度：MBAP  头部
                    byte[] headerBytes = new byte[6];
                    int count = _socket.Receive(headerBytes, 0, 6, SocketFlags.None);
                    if (count == 0)
                        throw new Exception("未接收到响应数据");
                    // 数据PDU部分数据
                    int pduLen = BitConverter.ToInt16(new byte[] { headerBytes[5], headerBytes[4] });
                    byte[] pduBytes = new byte[pduLen];
                    count = _socket.Receive(pduBytes, 0, pduLen, SocketFlags.None);
                    if (count == 0)
                        throw new Exception("未接收到响应数据");

                    result.Datas.AddRange(headerBytes);
                    result.Datas.AddRange(pduBytes);

                    callback?.Invoke(result);
                    Console.WriteLine("接收完成");
                }
            });
            Console.WriteLine("发送完成");
        }

        public BodaResult SendMsgc(byte[] bytes, int timeout = 1500)
        {
            throw new NotImplementedException();
        }

        public BodaResult<byte> ReceiveMsg(int timeout = 2000, int bytesLen = 1024)
        {
            throw new NotImplementedException();
        }
    }
}
